package com.ecreditpal.audio.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.HttpConnectionFactory;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.conn.*;
import org.apache.http.impl.io.DefaultHttpRequestWriterFactory;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.nio.conn.NoopIOSessionStrategy;
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.util.EntityUtils;

import javax.annotation.PostConstruct;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

/**
 * @author lifeng
 * @version 1.0 on 2017/10/19.
 */
@Slf4j
public class AsyncHttpClient {
    private static CloseableHttpAsyncClient httpClient = null;

    @PostConstruct
    public void initMethod() {
        if (httpClient == null) {
            synchronized (AsyncHttpClient.class) {
                if (httpClient == null) {
                    try {
                        httpClient = getHttpClient();
                    } catch (IOReactorException | KeyManagementException | NoSuchAlgorithmException e) {
                        log.error("get http client failed ", e);
                    }
                }
            }
        }
    }

    private AsyncHttpClient() {}

    public CloseableHttpAsyncClient getHttpClient() throws IOReactorException, KeyManagementException, NoSuchAlgorithmException {
        //注册访问协议相关的socket工厂
        Registry<ConnectionSocketFactory> socketFactoryRegistry =
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.INSTANCE)
                        .register("https", SSLConnectionSocketFactory.getSystemSocketFactory()).build();
        //HttpConnection工厂:配置写请求/解析相应处理器
        HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connectionFactory =
                new ManagedHttpClientConnectionFactory(DefaultHttpRequestWriterFactory.INSTANCE,
                        DefaultHttpResponseParserFactory.INSTANCE);
        //DNS解析器
        DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE;

        //配置io线程
        IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
                .setIoThreadCount(Runtime.getRuntime().availableProcessors())
                .build();

        ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);

        //默认请求配置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5 * 1000) // 设置链接超时时间
                .setSocketTimeout(15 * 1000)//设置等待数据超时时间
                .setConnectionRequestTimeout(5000)
                //设置从连接池获取连接的等待超时时间
                .build();

        //绕过证书验证，处理https请求
        SSLContext sslcontext = createIgnoreVerifySSL();

        // 设置协议http和https对应的处理socket链接工厂的对象
        Registry<SchemeIOSessionStrategy> sessionStrategyRegistry = RegistryBuilder.<SchemeIOSessionStrategy>create()
                .register("http", NoopIOSessionStrategy.INSTANCE)
                .register("https", new SSLIOSessionStrategy(sslcontext))
                .build();

        PoolingNHttpClientConnectionManager cm = new PoolingNHttpClientConnectionManager(ioReactor, null, sessionStrategyRegistry);
        cm.setMaxTotal(300);
        cm.setDefaultMaxPerRoute(300);

        CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom()
                .setConnectionManager(cm)
                .setDefaultRequestConfig(requestConfig)
                .setMaxConnTotal(1000)
                .setMaxConnPerRoute(1000)
                .build();

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    log.error("httpClient close failed ", e);
                }
            }
        });
        httpClient.start();

        return httpClient;
    }


    /**
     * 绕过验证
     *
     * @return
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    public SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext sc = SSLContext.getInstance("SSLv3");

        // 实现一个X509TrustManager接口，用于绕过验证，不用修改里面的方法
        X509TrustManager trustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(
                    java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                    String paramString) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(
                    java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                    String paramString) throws CertificateException {
            }

            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };
        sc.init(null, new TrustManager[]{trustManager}, null);
        return sc;
    }

    public static void sendHttpPost(String url, Map<String, String> params, FutureCallback<HttpResponse> callback) {
        HttpPost httpPost = new HttpPost(url);
        if (params != null && params.size() > 0) {
            List<NameValuePair> nameValuePair = new ArrayList<>(params.size());
            log.debug("http request url:{} , content:{}", url, nameValuePair.toString());
            for (String s : params.keySet()) {
                nameValuePair.add(new BasicNameValuePair(s, params.get(s)));
            }
            try {
                httpPost.setEntity(new UrlEncodedFormEntity(nameValuePair, "utf-8"));
            } catch (UnsupportedEncodingException e) {
                log.error("set http post entity failed ", e);
            }
        }
        httpClient.execute(httpPost, callback);
    }


    public static void sendHttpGet(String url, FutureCallback<HttpResponse> callback) {
        HttpGet get = new HttpGet(url);
        httpClient.execute(get, callback);
    }


    public static void main(String[] argv) throws Exception {
        //利用计数器阻塞主线程，在全部回调结束后再close
        final CountDownLatch countDownLatch = new CountDownLatch(4);

        //测试回调
        FutureCallback<HttpResponse> futureTask = callBack(countDownLatch);
        AsyncHttpClient util = new AsyncHttpClient();

//        sendHttpPost("http://php.weather.sina.com.cn/xml.php?city=%B1%B1%BE%A9&password=DJOYnieT8234jlsK&day=0", null, callback);
        sendHttpGet("https://www.baidu.com",futureTask);
        //阻塞主线程
        countDownLatch.await();
    }

    public static  FutureCallback<HttpResponse>  callBack(CountDownLatch countDownLatch) {
        FutureCallback<HttpResponse> callback = new FutureCallback<HttpResponse>() {
            public void completed(final HttpResponse response) {
                //计数器减
                countDownLatch.countDown();
                System.out.println(" callback thread id is : " + Thread.currentThread().getId());
                try {
                    String content = EntityUtils.toString(response.getEntity(), "UTF-8");
                    System.out.println(" response content is : " + content);
                } catch (IOException e) {
                    log.error("convert response entity failed ", e);
                }
            }

            public void failed(final Exception ex) {
                //计数器减
                countDownLatch.countDown();
                System.out.println(" callback thread id is : " + Thread.currentThread().getId());
            }

            public void cancelled() {
                //计数器减
                countDownLatch.countDown();
                System.out.println(" callback thread id is : " + Thread.currentThread().getId());
            }
        };

        return callback;
    }


    /**
     * 设置代理
     *
     * @param
     * @param hostOrIP
     * @param port
     */
    public static HttpAsyncClientBuilder proxy(String hostOrIP, int port) {
        // 依次是代理地址，代理端口号，协议类型
        HttpHost proxy = new HttpHost(hostOrIP, port, "http");
        DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
        return HttpAsyncClients.custom().setRoutePlanner(routePlanner);
    }
}
